# frozen_string_literal: true

require_relative '../../../spec_helper'

describe Wpxf::WordPress::FileDownload do
  let(:klass) do
    Class.new(Wpxf::Module) do
      include Wpxf::WordPress::FileDownload
    end
  end

  let(:subject) { klass.new }
  let(:export_path_required) { false }
  let(:http_res) { Wpxf::Net::HttpResponse.new(nil) }

  before :each, 'setup subject' do
    subject.set_option_value('check_wordpress_and_online', false)
    allow(subject).to receive(:working_directory).and_return('wp-content')
    allow(subject).to receive(:download_file).and_return(http_res)
    allow(subject).to receive(:emit_info)
    allow(subject).to receive(:emit_error)
    allow(subject).to receive(:emit_success)
    allow(FileUtils).to receive(:rm)
    subject.set_option_value('host', '127.0.0.1')
    subject.set_option_value('remote_file', '../../wp-config.php')
  end

  describe '#new' do
    context 'if #register_remote_file_option? is true' do
      it 'should register the remote_file option' do
        expect(subject.get_option('remote_file')).to_not be_nil
      end

      it 'should register a generic description' do
        expect(subject.module_desc).to match(/This module exploits a vulnerability/)
      end
    end

    context 'if #register_remote_file_option? is false' do
      before :each, 'setup mocks' do
        allow_any_instance_of(klass).to receive(:register_remote_file_option?).and_return(false)
      end

      it 'should not register the remote_file option' do
        subject = klass.new
        expect(subject.get_option('remote_file')).to be_nil
      end

      it 'should not register a generic description' do
        subject = klass.new
        expect(subject.module_desc).to_not match(/This module exploits a vulnerability/)
      end
    end
  end

  describe '#register_remote_file_option?' do
    it 'should return true by default' do
      expect(subject.register_remote_file_option?).to be true
    end
  end

  describe '#working_directory' do
    it 'should return nil by default' do
      subject = klass.new
      expect(subject.working_directory).to be_nil
    end
  end

  describe '#default_remote_file_path' do
    it 'should return nil by default' do
      expect(subject.default_remote_file_path).to be_nil
    end
  end

  describe '#downloader_url' do
    it 'should return nil by default' do
      expect(subject.downloader_url).to be_nil
    end
  end

  describe '#download_request_params' do
    it 'should return nil by default' do
      expect(subject.download_request_params).to be_nil
    end
  end

  describe '#download_request_body' do
    it 'should return nil by default' do
      expect(subject.download_request_body).to be_nil
    end
  end

  describe '#download_request_method' do
    it 'should return :get by default' do
      expect(subject.download_request_method).to eql :get
    end
  end

  describe '#remote_file' do
    it 'should return the value of the remote_file option' do
      subject.set_option_value('remote_file', 'test')
      expect(subject.remote_file).to eql 'test'
    end
  end

  describe '#validate_content' do
    it 'should return true by default' do
      expect(subject.validate_content(nil)).to be true
    end
  end

  describe '#before_download' do
    it 'should return true by default' do
      expect(subject.before_download).to be true
    end
  end

  describe '#file_extension' do
    it 'should return an empty string' do
      expect(subject.file_extension).to eql ''
    end
  end

  describe '#expected_http_code' do
    it 'should return 200 by default' do
      expect(subject.expected_http_code).to eql 200
    end
  end

  describe '#handle_unexpected_http_code' do
    it 'should return false by default' do
      expect(subject.handle_unexpected_http_code(0)).to be false
    end
  end

  describe '#file_category' do
    it' should return "unknown" by default' do
      expect(subject.file_category).to eql 'unknown'
    end
  end

  describe '#run' do
    context 'if #working_directory is not implemented' do
      context 'and #register_remote_file_option? is true' do
        it 'should raise an error ' do
          subject = klass.new
          expect { subject.run }.to raise_error('A value must be specified for #working_directory')
        end
      end

      context 'and #register_remote_file_option? is false' do
        it 'should not raise an error' do
          allow_any_instance_of(klass).to receive(:register_remote_file_option?).and_return(false)
          subject = klass.new
          expect { subject.run }.to_not raise_error
        end
      end
    end

    context 'if #before_download returns false' do
      it 'should return false' do
        allow(subject).to receive(:before_download).and_return(false)
        allow(subject).to receive(:working_directory).and_return('wp-content')
        expect(subject.run).to be false
      end
    end

    context 'if the http request is successful' do
      it 'should download the file to a file in the .wpxf directory' do
        http_res.code = 200
        allow(subject).to receive(:generate_unique_filename).and_return('unique_filename')
        subject.run

        expected = {
          method: subject.download_request_method,
          url: subject.downloader_url,
          params: subject.download_request_params,
          body: subject.download_request_body,
          cookie: subject.session_cookie,
          local_filename: 'unique_filename'
        }

        expect(subject).to have_received(:emit_info).with('Downloading file...')
        expect(subject).to have_received(:download_file)
          .with(expected)
          .exactly(1).times
      end
    end

    context 'if the http request times out' do
      before :each, 'setup response' do
        http_res.timed_out = true
      end

      it 'should emit an error' do
        subject.run
        expect(subject).to have_received(:emit_error)
          .with('Request timed out, try increasing the http_client_timeout')
          .exactly(1).times
      end

      it 'should return false' do
        expect(subject.run).to be false
      end
    end

    context 'if the http response is nil' do
      let(:http_res) { nil }

      it 'should emit an error' do
        subject.run
        expect(subject).to have_received(:emit_error)
          .with('Request timed out, try increasing the http_client_timeout')
          .exactly(1).times
      end

      it 'should return false' do
        expect(subject.run).to be false
      end
    end

    context 'if the http response code is not expected' do
      it 'should emit an error' do
        http_res.code = 404
        subject.run
        expect(subject).to have_received(:emit_error)
          .with('Server responded with code 404')
          .exactly(1).times
      end

      it 'should return false' do
        http_res.code = 404
        expect(subject.run).to be false
      end
    end

    context 'if the #validate_content process fails' do
      before :each, 'setup mocks' do
        http_res.code = 200
        allow(subject).to receive(:validate_content).and_return(false)
      end

      it 'should remove the downloaded file' do
        subject.run
        expect(FileUtils).to have_received(:rm)
          .with(anything, force: true)
          .exactly(1).times
      end

      it 'should return false' do
        expect(subject.run).to be false
      end
    end

    context 'if no errors occur' do
      before :each, 'setup mocks' do
        http_res.code = 200
        allow(subject).to receive(:generate_unique_filename).and_return('filename')
      end

      it 'should emit a success notice' do
        subject.run
        expect(subject).to have_received(:emit_success)
          .with('Downloaded file to filename')
          .exactly(1).times
      end

      it 'should store the path to the file as a loot item' do
        subject.run
        loot = Wpxf::Models::LootItem.first
        expect(loot.path).to eql 'filename'
      end

      context 'if #loot_description is implemented' do
        it 'should use it as the loot notes' do
          allow(subject).to receive(:loot_description).and_return('custom notes')
          subject.run
          loot = Wpxf::Models::LootItem.first
          expect(loot.notes).to eql 'custom notes'
        end
      end

      context 'if #loot_description is not implemented' do
        context 'and #register_remote_file_option? is true' do
          it 'should store the remote file name in the loot notes' do
            subject.run
            loot = Wpxf::Models::LootItem.first
            expect(loot.notes).to eql 'Remote file: wp-config.php'
          end
        end

        context 'and #register_remote_file_option? is false' do
          it 'should store empty loot notes' do
            allow(subject).to receive(:register_remote_file_option?).and_return(false)

            subject.run
            loot = Wpxf::Models::LootItem.first
            expect(loot.notes).to eql ''
          end
        end
      end

      it 'should return true' do
        expect(subject.run).to be true
      end
    end
  end
end
